/**
 * @Validate.js
 * @author zhangxinxu
 * @version
 * Created: 15-08-19
 */

(function (global, factory) {
    if (typeof define === 'function' && (define.amd || define.cmd)) {
        define(factory);
    } else {
        global.Validate = factory();
    }
}(this, function (require) {
    if (typeof require == 'function') {
        require('common/ui/ErrorTip');
    } else if (!$().follow) {
        if (window.console) {
            window.console.error('need ErrorTip.js');
        }

        return {};
    }

    /**
     * 基于HTML5规范的表单验证方法
     * 根据原生属性:type, required, min, max, minlength, maxlength, step, pattern等属性进行验证
     * 支持new实例构造以及jQuery包装器用法，例如：
     * new Validate(el, options);
     * or
     * $().validate();
     * 很多验证规则基于其他UI组件特征
     */


    // 包装器方法
    $.fn.validate = function(callback, options) {
        return $(this).each(function() {
            $(this).data('validate', new Validate($(this), callback, options));
        });
    };

    // 选中某范围文字内容的拓展方法
    $.fn.selectRange = function(start, end) {
        var that = $(this).get(0);
        if (that.createTextRange) {
            var range = that.createTextRange();
            range.collapse(true);
            range.moveEnd('character', end);
            range.moveStart('character', start);
            range.select();
        } else {
            that.focus();
            that.setSelectionRange(start, end);
        }

        return $(this);
    };

    // 是否含有某布尔属性方法
    $.fn.isProp = function(attr) {
        var prop = $(this).prop(attr) || $(this).attr(attr);
        if (prop || typeof prop == 'string') {
            return true;
        }

        return false;
    };

    // 全角转半角方法
    $.dbc2sbc = function(str) {
        var result = '';
        var i, code;
        for (i = 0 ; i < str.length; i++) {
            code = str.charCodeAt(i);
            if (code >= 65281 && code <= 65373) {
                result += String.fromCharCode(str.charCodeAt(i) - 65248);
            } else if (code == 12288) {
                result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);
            } else {
                result += str.charAt(i);
            }
        }

        return result;
    };

    // 获得并重置type
    $.getType = function(ele) {
        // 控件的类型
        // 这里不能直接ele.type获取，否则，类似email类型直接就是text类型
        // 但是，有些元素的type是隐式的，例如textarea，因此ele.type还是需要的
        var attrType = ele.getAttribute('type');
        var type = attrType || ele.type || '';
        // 高级版本IE浏览器的向下兼容模式，会忽略email类型
        // 虽然这种情况出现的可能性很小，但是，为了万一
        // 可以使用type='email '解决此问题
        // 所以，才会有下面的正则替换，得到最精准的type类型
        type = type.toLowerCase().replace(/\W+$/, '');

        // 对于本来支持的浏览器，需要变成真实的
        if (attrType && attrType != type && $('<input type="' + type + '">').attr('type') == type) {
            try {
                ele.type = type;
            } catch (e) {
                //
            }
        }

        // 类似_date_这种干掉浏览器默认类型的处理
        return type.replace(/_/g, '');
    };

    // 获得字符长度
    // @ele  Object   input/textarea表单控件元素
    // @max  Number   返回满足最大值时候的真实字符长度
    $.getLength = function(ele, max) {
        if (ele.type == 'password') {
            return max ? max : ele.value.length;
        }
        // 语言属性和trim后的值
        var lang = ele.getAttribute('lang');
        var value = $.trim(ele.value);
        if (!lang) {
            return max ? max : value.length;
        }
        if (value == '') {
            return 0;
        }

        // 中文和英文计算字符个数的比例
        var ratioCh = 1;
        var ratioEn = 1;

        if (/zh/i.test(lang)) {
            // 1个英文半个字符
            ratioEn = 0.5;
        } else if (/en/i.test(lang)) {
            // 1个中文2个字符
            ratioCh = 2;
        }

        // 下面是中文或全角等字符正则
        if (!max) {
            var lenOriginCh = value.replace(/[\x00-\xff]/g, '').length;
            var lenOriginEn = value.length - lenOriginCh;

            // 根据比例返回最终长度
            return Math.ceil(lenOriginEn * ratioEn) + Math.ceil(lenOriginCh * ratioCh);
        }
        var start = 0;
        var lenMatch = max;

        $.each(value.split(''), function(index, letter) {
            if (start >= max) return;
            if (/[\x00-\xff]/.test(letter)) {
                // 如果字符是中文或全角
                start += ratioEn;
            } else {
                // 如果字符是英文
                start += ratioCh;
            }

            if (start >= max) {
                lenMatch = index + 1;
            }
        });

        return lenMatch;
    };

    // 电话号码过滤成符合要求的号码，省去用户自己处理
    // 例如我们从某个地方复制电话号码，可能是短横线相连
    // 或者前面带+86
    $.telFilter = function (tel) {
        tel = tel || '';
        tel = tel.replace('+86', '');
        // 如果此时剩余所有数字正好11位
        if (tel.match(/\d/) && tel.match(/\d/g).length == 11) {
            tel = tel.replace(/\D/g, '');
        }
        return tel;
    };

    // 一些全局的属性和方法
    $.validate = (function() {
        return {
            reg: {
                email: '^[a-z0-9._%-]+@([a-z0-9-]+\\.)+[a-z]{2,4}$',
                number: '^\\-?\\d+(\\.\\d+)?$',
                url: "^(http|https)\\:\\/\\/[a-z0-9\\-\\.]+\\.[a-z]{2,3}(:[a-z0-9]*)?\\/?([a-z0-9\\-\\._\\:\\?\\,\\'\\/\\\\\\+&amp;%\\$#\\=~])*$",
                tel: '^1\\d{10}$',
                zipcode: '^\\d{6}$',
                date: '^\\d{4}\\-(0\\d|1[0-2])\\-([0-2]\\d|3[0-1])$',
                time: '^[0-2]\\d\\:[0-5]\\d$',
                hour: '^[0-2]\\d\\:00$',
                minute: '^[0-2]\\d\\:[0-5]\\d$',
                'date-range': '^\\d{4}(\\-\\d{2}){2}\\s至\\s\\d{4}(\\-\\d{2}){2}$',
                'month-range': '^\\d{4}\\-\\d{2}\\s至\\s\\d{4}\\-\\d{2}$'
            },

            prompt: function(result, ele, options) {

                /*
                 * @result  是验证返回的结果，为纯对象
                            {
                                type: '',
                                error: ''
                            }
                            有对应的提示信息
                 * @ele     是当前出错的元素, 此参数可选
                            当该属性存在的时候，文字提示内容更丰富与智能
                 * @options 自定义的提示数据，优先级最高, 需要和ele参数配合使用
                */
                var defaultPrompt = {
                    name: $.extend({}, {
                        email: '邮箱',
                        tel: '手机号码',
                        url: '网址',
                        zipcode: '邮编',
                        password: '密码',
                        number: '数值',
                        range: '数值',
                        date: '日期',
                        year: '年份',
                        month: '月份',
                        hour: '小时',
                        minute: '分钟',
                        time: '时间',
                        datetime: '日期时间',
                        'date-range': '日期范围',
                        'month-range': '月份范围'
                    }, $.validate.name || {}),
                    ignore: {
                        radio: '请选择一个选项',
                        checkbox: '如果要继续，请选中此框',
                        select: '请选择列表中的一项',
                        'select-one': '请选择列表中的一项',
                        empty: '请填写此字段'
                    },
                    unmatch: {
                        pattern: '内容格式不符合要求',
                        multiple: '某项内容格式不符合要求'
                    },
                    out: {
                        min: '值偏小',
                        max: '值偏大',
                        step: '值不符合要求'
                    },
                    overflow: {
                        minlength: '内容长度偏小',
                        maxlength: '内容长度偏大'
                    }
                };

                if (!result) {
                    return;
                }
                // 最终的提示文字

                // 元素的id, type等
                var text = '';
                var id = ele.id;
                var type = '';
                var error = result.error;

                if (ele) {
                    type = ele.getAttribute('type') || ele.type || '';
                    type = $.trim(type);
                    if (type == 'select-one') type = 'select';
                }

                // options对应的提示信息
                options = options || {};
                var optionsPrompt = options.prompt || options;

                if (id && $.isArray(options)) {
                    $.each(options, function(index, obj) {
                        if (obj.id == id) {
                            optionsPrompt = obj.prompt;
                        }
                    });
                }

                // 错误提示关键字
                var name = defaultPrompt.name[type] || (function() {
                    if (!options.label || !id || /checkbox|radio/.test(type)) return;
                    var text = '';
                    // 从label动态获取提示数据
                    $('label[for=' + id + ']').each(function() {
                        var label = $(this).clone();
                        // 只使用裸露的文字作为提示关键字
                        label.children().remove();
                        // 去除数字和冒号
                        var labelText = $.trim(label.html()).replace(/\d/g, '').replace('：', '');
                        // 内容最长的那个元素作为提示文字
                        if (labelText.length > text.length) {
                            text = labelText;
                        }
                    });
                    // 提示关键字显然不会小于2个字
                    if (text.length >= 2) {
                        return text;
                    }
                })();

                switch (result.type) {
                    case 'ignore': {
                        // 自定义提示
                        text = optionsPrompt.ignore;
                        // 如果没有对应的自定义提示
                        if (!text) {
                            // 则使用默认的提示：
                            // 1. 根据类型关键字组装的提示
                            if (type && name) {
                                if (type != 'select') {
                                    text = name + '不能为空';
                                } else {
                                    text = defaultPrompt.ignore[type] = '您尚未选择' + name;
                                }
                            } else {
                                // 2. 默认的ignore几个错误提示
                                text = defaultPrompt.ignore[error];
                            }

                            text = text || defaultPrompt.ignore.empty;
                        }

                        break;
                    }
                    case 'unmatch': {
                        // 这里的默认错误提示没有一个和type关联
                        // 因此，逻辑跟上面的是有差异的

                        // 首先，优先使用自定义提示是一样的
                        text = optionsPrompt.unmatch;
                        if (!text) {
                            // 然后
                            if (type && name) {
                                // 1. 试试关键字提示
                                text = name + '格式不符合要求';
                            } else {
                                // 2. 宽泛的默认提示
                                text = defaultPrompt.unmatch[error] || defaultPrompt.unmatch.pattern;
                            }
                            text = text || defaultPrompt.ignore.empty;
                        }
                        break;
                    }
                    case 'out': {
                        // 先看看有没有自定义的提示
                        text = optionsPrompt.out && optionsPrompt.out[error];

                        if (!text) {
                            // out可以是range类型, date类型，number类型
                            if (type && name) {
                                var min = ele.getAttribute('min');
                                var max = ele.getAttribute('max');
                                var step = ele.getAttribute('step') * 1 || 1;

                                if (type == 'month-range') {
                                    min = min.slice(0, 7);
                                    max = max.slice(0, 7);
                                }

                                var textBigger = '必须要大于或等于' + min;
                                var textSmaller = '必须要小于或等于' + max;

                                if (error == 'min') {
                                    text = name + textBigger;
                                    if (type.slice(-6) == '-range') {
                                        text = '起始日期' + textBigger;
                                    }
                                } else if (error == 'max') {
                                    text = name + '必须要小于或等于' + max;
                                    if (type.slice(-6) == '-range') {
                                        text = '结束日期' + textSmaller;
                                    }
                                } else if (error == 'min-max') {
                                    text = '起始日期' + textBigger + '，结束日期' + textSmaller;
                                } else if (error == 'step') {
                                    if (type == 'number' || type == 'range') {
                                        text = '请输入有效的值。两个最接近的有效值是' + (function() {
                                            min = min * 1;
                                            max = max * 1;
                                            var value = $.trim(ele.value) * 1;
                                            var closest = min;
                                            for (var start = min; start < max; start += step) {
                                                if (start < value && (start + step) > value) {
                                                    closest = start;
                                                    break;
                                                }
                                            }

                                            return [closest, closest + step].join('和');
                                        })();
                                    } else {
                                        text = '请选择有效的值。' + name + '间隔是' + step;
                                    }
                                }
                            } else {
                                // 纯粹使用含糊的默认提示
                                text = defaultPrompt.out[error];
                            }
                        }

                        text = text || defaultPrompt.out.step;
                        break;
                    }
                    case 'overflow': {
                        // 先看看有没有自定义的提示
                        text = optionsPrompt.overflow && optionsPrompt.overflow[error];
                        if (!text) {
                            var minlength = ele.getAttribute('minlength');
                            var maxlength = ele.maxlength || ele.getAttribute('maxlength');

                            // 关于字符个数说明
                            var lang = ele.getAttribute('lang');
                            var textCharLength = '';

                            if (/zh/i.test(lang)) {
                                textCharLength = '个汉字(2字母=1汉字)';
                            } else if (/en/i.test(lang)) {
                                textCharLength = '个字符(1汉字=2字符)';
                            }

                            if (error == 'minlength') {
                                text = '内容长度不能小于' + minlength + textCharLength;
                            } else if (error == 'maxlength') {
                                text = '内容长度不能大于' + maxlength.replace(/\D/g, '') + textCharLength;
                            }
                        }
                        text = text || defaultPrompt.overflow[error];
                        break;
                    }
                }

                if ($.isFunction(text)) {
                    text = text.call(ele, $(ele));
                }

                return text;
            },

            /*
            ** 验证一般包括下面几个个方向：
            ** 1. 是否required同时值为空
            ** 2. 是否数据匹配错误(pattern, type)
            ** 3. 是否超出范围(min/max/step)
            ** 4. 内容超出限制(minlength, maxlength)
            ** 下面的这些方法就是上面3个方向判断
            ** 其中，参数ele为DOM对象
            ** 返回数据规则如下：
            ** 如果没有把柄，返回false;
            ** 如果真的有误，则返回错误类别：{
                  type: ''
               }
            }
            */

            /**
             * 判断元素是否为空的验证
             * @param  {Element}  ele 验证的DOM元素
             * @return {Boolean|Object} 返回验证通过布尔值或者出错信息对象
             */
            isIgnore: function(ele) {
                // 主要针对required必填或必选属性的验证
                if (!ele || ele.disabled) return false;

                // 类型
                var type = $.getType(ele);

                // 此时的值才是准确的值（浏览器可能会忽略文本框中的值，例如number类型输入框中的不合法数值）
                var el = $(ele);
                var value = ele.value;

                if (el.isProp('required')) {

                    // 根据控件类型进行验证
                    // 1. 单复选框比较特殊，先处理下
                    if (type == 'radio') {
                        // 单选框，只需验证是否必选，同一name单选组只有要一个设置required即可
                        var eleRadios = self;
                        var eleForm = el.parents('form');

                        if (ele.name && eleForm.length) {
                            eleRadios = eleForm.find("input[type='radio'][name='" + ele.name + "']");
                        }

                        // 如果单选框们没有一个选中，则认为无视了required属性
                        var atLeastOneRadioChecked = false;

                        eleRadios.each(function() {
                            if (atLeastOneRadioChecked == false && $(this).prop('checked')) {
                                atLeastOneRadioChecked = true;
                            }
                        });

                        if (atLeastOneRadioChecked == false) {
                            // 没有一个单选框选中
                            return {
                                type: 'ignore',
                                error: 'radio'
                            };
                        }

                        return false;
                    } else if (type == 'checkbox') {
                        // 复选框是每一个都必须选中
                        if (el.prop('checked') == false) {
                            return {
                                type: 'ignore',
                                error: 'checkbox'
                            };
                        }

                        return false;
                    } else if (/select/.test(type) && value == '') {
                        return {
                            type: 'ignore',
                            error: 'select'
                        };
                    } else if (type != 'password') {
                        value = $.trim(value);
                    }

                    if (value == '') {
                        // 重新赋值，清空可能被浏览器默认认为不合法的值
                        // 但是需要是现代浏览器，低版本IE会不断触发属性改变事件
                        if (history.pushState) ele.value = '';

                        // 返回验证结果
                        return {
                            type: 'ignore',
                            error: 'empty'
                        };
                    }
                }

                return false;
            },

            /**
             * 判断元素值的合法性
             * @param  {Element}  ele 验证的DOM元素
             * @param  {RegExp} regex 验证的正则
             * @return {Boolean|Object} 返回验证通过布尔值或者出错信息对象
             */
            isUnmatch: function(ele, regex, params) {
                // 禁用元素不参与验证
                if (!ele || ele.disabled) return false;

                var el = $(ele);

                // 原始值和处理值
                var inputValue = ele.value;
                var dealValue = inputValue;

                // 类型
                var type = $.getType(ele);

                // 特殊控件不参与验证
                if (/^radio|checkbox|select$/i.test(type)) return false;

                // 全角半角以及前后空格的处理
                // 适用于type=url/email/tel/zipcode/number/date/time等
                if (type != 'password') {
                    dealValue = $.trim(inputValue);
                }
                if (/^text|textarea|password$/i.test(type) == false) {
                    dealValue = $.dbc2sbc(dealValue);
                }
                if (type == 'tel') {
                    dealValue = $.telFilter(dealValue);
                }

                //  文本框值改变，重新赋值
                // 即时验证的时候，不赋值，否则，无法输入空格
                if ($.validate.focusable !== false && $.validate.focusable !== 0 && dealValue != inputValue) {
                    ele.value = dealValue;
                }

                // 如果没有值，则认为通过，没有错误
                if (dealValue == '') return false;

                // 获取正则表达式，pattern属性获取优先，然后通过type类型匹配。注意，不处理为空的情况
                regex = regex || (function() {
                    return el.attr('pattern');
                })() ||
                (function() {
                    // 文本框类型处理，可能有管道符——多类型重叠，如手机或邮箱
                    return type && $.map(type.split('|'), function(typeSplit) {
                        var matchRegex = $.validate.reg[typeSplit];
                        if (matchRegex) {
                            return matchRegex;
                        }
                    }).join('|');
                })();

                // 如果没有正则匹配的表达式，就没有验证的说法，认为出错为false
                if (!regex) return false;

                // multiple多数据的处理
                var isMultiple = el.isProp('multiple');
                var newRegExp = new RegExp(regex, params || 'i');
                // number,range等类型下multiple是无效的
                if (isMultiple && /^number|range$/i.test(type) == false) {
                    var isAllPass = true;
                    $.each(dealValue.split(','), function(i, partValue) {
                        partValue = $.trim(partValue);
                        if (isAllPass && !newRegExp.test(partValue)) {
                            isAllPass = false;
                        }
                    });
                    // 有数据没有匹配
                    if (isAllPass == false) {
                        return {
                            type: 'unmatch',
                            error: 'multiple'
                        };
                    }
                } else if (newRegExp.test(dealValue) == false) {
                    return {
                        type: 'unmatch',
                        error: 'pattern'
                    };
                }

                return false;
            },

            /**
             * 判断数值或日期范围超出
             * @param  {Element}  ele 验证的DOM元素
             * @return {Boolean|Object} 返回验证通过布尔值或者出错信息对象
             */
            isOut: function(ele) {
                // 是否数值或日期范围超出
                if (!ele || ele.disabled || /^radio|checkbox|select$/i.test(ele.type)) return false;

                var el = $(ele);

                var attrMin = el.attr('min');
                var attrMax = el.attr('max');
                var attrStep = Number($(ele).attr('step')) || 1;
                // 值
                var type = $.getType(ele);
                var value = ele.value;

                if (type.slice(-6) != '-range') {
                    if (value == '0' || Number(value) == value) {
                        value = value * 1;
                    }
                    if (attrMin && value < attrMin) {
                        return {
                            type: 'out',
                            error: 'min'
                        };
                    } else if (attrMax && value > attrMax) {
                        return {
                            type: 'out',
                            error: 'max'
                        };
                    }

                    // number, range类型的范围

                    if ((type == 'number' || type == 'range') && attrStep && attrMin &&
                        !/^\d+$/.test(Math.abs(value - attrMin) / attrStep)
                    ) {
                        return {
                            type: 'out',
                            error: 'step'
                        };
                    }
                    // hour, minute, time类型的范围
                    if ((type == 'hour' || type == 'minute' || type == 'time') && attrMin && attrStep) {
                        var minuteValue = value.split(':')[1];
                        var minuteMin = attrMin.split(':')[1];

                        if (type == 'hour' && (minuteValue != minuteMin || (value.split(':')[0] - attrMin.split(':')[0]) % attrStep != 0)) {
                            // 小时，则是分钟要一致，小时数正好step
                            return {
                                type: 'out',
                                error: 'step'
                            };
                        } else if ((minuteValue - minuteMin) % attrStep !== 0) {
                            return {
                                type: 'out',
                                error: 'step'
                            };
                        }
                    }
                } else {
                    // 时间范围
                    var arrSplitValue = value.split(' ');
                    var error = [];

                    // 防止month-range时候，min/max设置的是完整时间，
                    // 如2017-07-07，而不是2017-07
                    if (type == 'month-range') {
                        attrMin = attrMin && attrMin.slice(0, 7);
                        attrMax = attrMax && attrMax.slice(0, 7);
                    }

                    if (arrSplitValue.length == 3) {
                        if (attrMin && arrSplitValue[0] < attrMin) {
                            error.push('min');
                        }
                        if (attrMax && arrSplitValue[2] > attrMax) {
                            error.push('max');
                        }
                        if (error.length) {
                            return {
                                type: 'out',
                                error: error.join('-')
                            };
                        }
                    }
                }

                return false;
            },

            /**
             * 内容是否超出长度限制的判断
             * @param  {Element}  ele   DOM元素对象
             * @return {Boolean|Object} 返回验证通过布尔值或者出错信息对象
             */
            isOverflow: function(ele) {
                // 是否内容长度溢出的判断
                if (!ele || ele.disabled || /^radio|checkbox|select$/i.test(ele.type)) return false;

                var el = $(ele);
                //  大小限制
                var attrDataMin = el.attr('minlength');
                var attrDataMax = ele.maxlength || el.attr('maxlength');
                // 值
                var value = ele.value;

                if (value == '') {
                    return false;
                }

                var length = $.getLength(ele);

                if (attrDataMin && length < attrDataMin) {
                    return {
                        type: 'overflow',
                        error: 'minlength'
                    };
                }

                if (attrDataMax) {
                    attrDataMax = attrDataMax.replace(/\D/g, '');
                    if (length > attrDataMax) {
                        return {
                            type: 'overflow',
                            error: 'maxlength'
                        };
                    }
                }

                return false;
            },

            /**
             * 判断单个DOM元素是否验证通过
             * @param  {Element}  ele DOM元素对象
             * @return {Boolean}      是否验证通过
             */
            isError: function(ele) {
                // 1. 元素不存在不验证
                // 2. 禁用态表单元素不参与验证
                if (!ele || ele.disabled) {
                    return false;
                }

                // 3. 提交、重置等元素不参与验证
                var type = ele.getAttribute('type') || ele.type;
                var tag = ele.tagName.toLowerCase();

                if (/^button|submit|reset|file|image$/.test(type) == true || tag == 'button') return false;

                return $.validate.isIgnore(ele) || $.validate.isUnmatch(ele) || $.validate.isOut(ele) || $.validate.isOverflow(ele) || false;
            },

            /**
             * 系列元素是否验证通过
             * @param  {Element|Object}  el      [description]
             * @param  {Object}  options [description]
             * @return {Boolean}         [description]
             */
            isAllPass: function(el, options) {
                // el可以是独立的DOM, 也可以是form元素，也可以是jQuery序列元素
                if (!el) {
                    return true;
                }

                el = $(el);
                if (el.is('form')) {
                    el = el.find(':input');
                }

                // 遍历以及验证
                var isAllPass = true;

                el.each(function() {
                    var ele = this;
                    // 一些属性

                    // 实现现代浏览器下利用:invalid选择器即时响应效果
                    var type = ele.getAttribute('type') || ele.type;
                    var pattern = ele.getAttribute('pattern');

                    if (!pattern && type) {
                        pattern = $.validate.reg[type];
                        if (pattern) {
                            ele.setAttribute('pattern', pattern);
                        }
                    }

                    // 1. 采用单验证模式，所以，一旦发现isAllPass为false, 我们停止接下来的验证
                    // 2. 禁用态的元素不验证
                    // 3. 提交按钮、重置按钮等不验证
                    if (isAllPass == false || ele.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;

                    var result = $.validate.isError(ele);
                    // 当我们希望进行全局验证，又不希望出现tips提示的时候
                    // 可以这么调用 $.validate.isAllPass(el, false);
                    if (result && options !== false) {
                        // 提示错误
                        $.validate.errorTip(ele, $.validate.prompt(result, ele, options));
                        isAllPass = false;
                    }
                });

                return isAllPass;
            },

            /**
             * 计数效果
             * @param  {Object} el 需要计数的元素
             * @return {Object}    返回当前上下文
             */
            count: function(el) {
                if (!el) {
                    return this;
                }
                var ele;
                // 计数相关代码构建
                // el只能是单个包装器对象，其余的会忽略
                if (el.get) {
                    ele = el.get(0);
                } else {
                    ele = el;
                    el = $(el);
                }

                // 标签名
                var tag = ele.tagName.toLowerCase();

                // 首先ele需要是表单元素
                if (tag != 'input' && tag != 'textarea') {
                    return this;
                }

                // 获得限制大小
                var min = el.attr('minlength') || 0;
                var max = ele.maxlength || el.attr('maxlength');

                if (!min && !max) {
                    return this;
                }
                // 过滤非数值字符
                max = max.replace(/\D/g, '');

                // 类名前缀
                var clPrefix = 'ui-';
                // 其他类名
                var cl = clPrefix + tag;
                var clBox = cl + '-x';
                var clCount = cl + '-count';

                // 存在下面3种情况
                // 1. 普通文本框需要变身
                // 2. 缺少计数label
                // 3. HTML已经完整
                var elLabel, elMin;
                // , elMax
                if (el.hasClass(cl)) {
                    // 情况1先不支持
                    return this;
                }
                // 情况2
                // ui-input-x
                //    input
                //    div.ui-input
                // 缺少label，也就是数值元素
                if (el.parent('.' + clBox).length == 0) {
                    return this;
                }

                // 元素id必须
                var id = ele.id;
                if (!id) {
                    // 没有id随机id
                    id = 'id' + (Math.random() + '').replace(/\D/g, '');
                    ele.id = id;
                }

                elLabel = el.parent().find('.' + clCount);
                if (elLabel.length == 0) {
                    // 生成并载入计数元素
                    elLabel = $('<label for="' + id + '" class="ui-' + tag + '-count">\
                    <span>' + min + '</span>/<span>' + max + '</span>\
                </label>');
                    el.parent().append(elLabel);
                } else {
                    elLabel.attr('for', id);
                }
                elMin = elLabel.find('span').eq(0);
                //elMax = eleLabel.find('span').eq(1);

                // 目标元素
                //var target = $.validate.getTarget(el);
                // 计数，标红方法
                var fnCount = function() {
                    var length = $.getLength(ele);
                    elMin.html(length);
                    if (length > max) {
                        elMin.addClass('red');
                        // 下面注释是因为不需要即时变红，只要文字提示就可以
                        // 当验证组件开启即时验证之后，会标红的
                        //target.addClass('error');
                    } else {
                        elMin.removeClass('red');
                        //target.removeClass('error');
                    }
                };
                // 事件
                var flagCount = 'countBind';
                if (!el.data(flagCount)) {
                    if ('oninput' in document.createElement('div')) {
                        el.bind('input', function() {
                            fnCount();
                        });
                    } else {
                        ele.attachEvent('onpropertychange', function(event) {
                            if (event && event.propertyName == 'value') {
                                fnCount();
                            }
                        });
                    }
                    el.data(flagCount, true);
                }

                fnCount();

                return this;
            },

            /**
             * 显示出错信息处理
             * @param  {Object} el      出错提示原始元素（这里可能会进行转移）
             * @param  {String} content 出错提示内容
             * @return {Object}         返回当前上下文
             */
            errorTip: function(el, content) {
                var target = $.validate.getTarget(el);
                if (target.length == 0) {
                    return this;
                }

                var show = function() {
                    target.errorTip(content, {
                        onShow: function(tips) {
                            // 如果tips宽度比较小，居左定位
                            // 否则，使用默认的居中
                            var pffsetx = 0.5 * (tips.width() - target.width());
                            if (pffsetx < 0) {
                                tips.css('margin-left', 0.5 * (tips.width() - target.width()));
                            } else {
                                tips.css('margin-left', 0);
                            }

                            if ($.validate.focusable === false) {
                                tips.addClass('none');
                            } else {
                                tips.removeClass('none');
                            }
                        },
                        onHide: function() {
                            // 前提是开启了即时验证
                            if (!target.parents('form').data('immediate')) {
                                return;
                            }
                            // 自定义验证方法的即时验证
                            var customValidate = $.validate.el.control.data('customValidate');
                            var result = customValidate && customValidate($.validate.el.control);
                            if ($.validate.isError($.validate.el.control.get(0)) || (typeof result == 'string' && result.length)) {
                                $.validate.el.target.addClass('error');
                            }

                            target.removeAttr('aria-label');
                        }
                    });

                    // 即时验证不需要focus与选中
                    if ($.validate.focusable === false || $.validate.focusable === 0) {
                        return;
                    }
                    $.validate.focusable = null;

                    var el = $.validate.el.control;
                    var ele = el.get(0);
                    var type = ele.getAttribute('type') || ele.type;

                    if (!type) {
                        return;
                    }

                    // 如果是内容超出，选中超出的文字内容
                    if (content.indexOf('内容长度') != -1 && content.indexOf('大') != -1) {
                        var value = el.val();
                        var length = value.length;
                        // var lang = el.attr('lang');
                        // 中文和英文计算字符个数的比例
                        // var ratioCh = 1;
                        // var ratioEn = 1;
                        // if (/zh/i.test(lang)) {
                        //     // 1个英文半个字符
                        //     ratioEn = 0.5;
                        // } else if (/en/i.test(lang)) {
                        //     // 1个中文2个字符
                        //     ratioCh = 2;
                        // }
                        // 获得最大长度的索引位置
                        var maxlength = ele.maxlength || el.attr('maxlength').replace(/\D/g, '');

                        if (length && maxlength) {
                            el.selectRange($.getLength(ele, maxlength), length);
                        }
                    } else {
                        el.select();
                    }
                };

                $.validate.el = {
                    control: $(el),
                    target: target
                };

                // 1. 首先，确保el在屏幕内，且需要预留tips的显示的足够距离，这里使用50像素
                var rect = target.get(0).getBoundingClientRect();
                if (rect.top < 50) {
                    $('html, body').animate({
                        'scrollTop': $(window).scrollTop() - (50 - rect.top)
                    }, 200, show);
                } else if (rect.bottom > $(window).height()) {
                    $('html, body').animate({
                        'scrollTop': $(window).scrollTop() + (rect.bottom - $(window).height())
                    }, 200, show);
                } else {
                    show();
                }

                return this;
            },

            /**
             * 获得对应展示的元素
             * @param  {Object} el 元素
             * @return {Object}    返回对应的展示元素（可能就是自身）
             */
            getTarget: function(el) {
                var ele = el;
                if (!el) return $();
                if (el.get) {
                    ele = el.get(0);
                } else {
                    el = $(el);
                }
                if (el.length == 0) return;

                var target = el;

                // 根据原生控件元素，确定真实的自定义控件元素
                // 这里的很多处理跟其他表单UI组件想耦合、关联
                var type = ele.getAttribute('type') || ele.type;
                var id = ele.id;
                var tag = ele.tagName.toLowerCase();
                // 1. 单复选框
                if (type == 'radio') {
                    target = el.parent().find('label.ui-radio[for=' + id + ']');
                } else if (type == 'checkbox') {
                    target = el.parent().find('label.ui-checkbox[for=' + id + ']');
                // 下拉框
                } else if (type == 'select' || tag == 'select') {
                    target = el.next('.ui-select');
                // range范围选择框
                } else if (type == 'range') {
                    target = el.prev('.ui-range');
                } else if (type == 'hidden' || el.css('display') == 'none') {
                    if (el.data('target') && el.data('target').size) {
                        target = $.validate.getTarget(el.data('target'));
                    }
                } else if (type == 'textarea' || tag == 'textarea') {
                    if (el.nextAll('.ui-textarea').length) {
                        target = el.nextAll('.ui-textarea').eq(0);
                    } else if (!document.querySelector && el.parent('.ui-textarea').length) {
                        target = el.parent('.ui-textarea');
                    }
                } else if (tag == 'input') {
                    if (el.nextAll('.ui-input').length) {
                        target = el.nextAll('.ui-input').eq(0);
                    } else if (!document.querySelector && el.parent('.ui-input').length) {
                        target = el.parent('.ui-input');
                    }
                }

                return target;
            }
        };
    })();

    /**
     * 验证实例方法主体
     * @param {Object}   el       通常值验证的表单元素
     * @param {Function} callback 验证成功的回调
     * @param {Object}   options  可选参数
     */
    var Validate = function(el, callback, options) {
        // el一般指的是form元素
        if (!el.eq) el = $();

        callback = callback || $.noop;

        // 干掉浏览器默认的验证
        el.attr('novalidate', 'novalidate');

        var defaults = {
            // 提交时候是全部出错红色高亮，还是仅第一个，默认是全部
            multiple: true,
            // 是否开启即时验证
            immediate: true,
            // 是否利用label关联元素的innerHTML作为提示关键字
            label: false,
            validate: [
            // 下面为结构示意
            /*{
                id: '',
                prompt: {
                    ignore: '',
                    unmatch: '',
                    out: {
                        min: '',
                        max: '',
                        step: ''
                    },
                    overflow: {
                        minlength: '',
                        maxlength: ''
                    }
                },
                method: $.noop
            }*/
            ],
            onError: $.noop,
            onSuccess: $.noop
        };

        var params = $.extend({}, defaults, options || {});

        // 还原禁用的提交和关闭按钮
        el.find(':disabled').each(function() {
            if (/^image|submit$/i.test(this.type)) {
                $(this).removeAttr('disabled');
            }
        });

        var self = this;
        // 干掉默认的提交
        el.on('submit', function(event) {
            if (self.stopValidate) {
                return;
            }
            event.preventDefault();

            if (self.isAllPass() && $.isFunction(callback)) {
                callback.call(this, self);
            }

            return false;
        });


        // 暴露的数据
        this.el = {
            form: el
        };

        this.callback = {
            error: params.onError,
            success: params.onSuccess
        };

        this.data = params.validate;

        this.boo = {
            multiple: params.multiple,
            immediate: params.immediate,
            label: params.label
        };

        // 计数效果
        this.count();

        // 手机号过滤等增强体验功能
        this.enhance();

        return this;
    };

    /**
     * 表单内有计数功能元素的处理
     * @return {Object} 返回当前实例
     */
    Validate.prototype.count = function() {
        // 计数效果
        var self = this;
        // 即时验证
        var form = this.el.form;
        // 遍历计数元素
        form.find('.ui-input-x > input, .ui-textarea-x > textarea').each(function() {
            var ele = this;
            var el = $(ele);
            // 对maxlength进行处理
            var maxlength = el.attr('maxlength');
            if (maxlength) {
                // 给maxlength设置不合法的值，阻止默认的长度限制
                try {
                    el.attr('maxlength', '_' + maxlength + '_');
                } catch(e) {
                    // IE7不能设置不合法的maxlength值 %>_<%
                    el.removeAttr('maxlength')[0].maxlength = maxlength;
                }
            }
            $.validate.count(el);
        });

        return self;
    };

    /**
     * 表单内一些元素体验增强处理
     * @return {Object} 返回当前实例
     */
    Validate.prototype.enhance = function () {
        var self = this;
        // 即时验证
        var form = self.el.form;

        form.find(':input').each(function() {
            // 元素
            var ele = this;
            // var el = $(ele);
            // type类型筛选
            // var type = ele.type;
            var attrType = ele.getAttribute('type');

            // 手机号码粘贴的优化处理
            if (ele.addEventListener && /^checkbox|radio|range$/i.test(attrType) == false) {
                ele.addEventListener('paste', function (event) {
                    // 剪切板数据对象
                    // 输入框类型
                    var type = this.getAttribute('type') || this.type;
                    // 剪切板数据对象
                    var clipboardData = event.clipboardData || window.clipboardData;
                    // 粘贴内容
                    var paste = '';
                    // 剪切板对象可以获取
                    if (!clipboardData || ele.disabled == true) {
                        return;
                    }
                    // 获取选中的文本内容
                    var textSelected = '';
                    if (window.getSelection) {
                        // 现代浏览器
                        // 直接window.getSelection().toString()对于IE的输入框无效
                        textSelected = this.value.slice(ele.selectionStart, ele.selectionEnd);
                    } else if (document.selection) {
                        // 旧IE浏览器
                        textSelected = document.selection.createRange().text;
                    }
                    // 只有输入框没有数据，或全选状态才处理
                    if (this.value.trim() == '' || textSelected === this.value) {
                        // 阻止冒泡和默认粘贴行为
                        event.preventDefault();
                        event.stopPropagation();
                        // 获取粘贴数据
                        paste = clipboardData.getData('text') || '';
                        // 进行如下处理
                        // 除非是password，其他都过滤前后空格
                        if (type != 'password') {
                            paste = paste.trim();
                        }
                        // 邮箱处理，可能会使用#代替@避免被爬虫抓取
                        if (type == 'email') {
                            paste = paste.replace('#', '@');
                        } else if (type == 'tel') {
                            // 手机号处理
                            paste = $.telFilter(paste);
                        }

                        // 插入
                        this.value = paste;
                    }
                    
                    $(this).trigger('input');
                });
            }
        });
    };

    /**
     * 表单即时验证的细节处理
     * @return {Object} 返回当前实例
     */
    Validate.prototype.immediate = function() {
        var self = this;
        // 即时验证
        var form = this.el.form;

        if (form.data('immediate')) {
            return this;
        }

        form.find(':input').each(function() {
            // 元素
            var ele = this;
            var el = $(ele);
            // type类型筛选
            var type = ele.type;
            var attrType = ele.getAttribute('type');
            // 给每个控件绑定即时验证
            if (type == 'button' || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;

            // 不同类别不同事件
            if (type == 'radio' || type == 'checkbox') {
                el.on('click', function() {
                    if (self.boo.immediate == false) return;
                    var isPass = self.isPass($(this));
                    if (isPass) {
                        self.isError($(this), false);
                    }
                });
            } else if (/select/.test(type) || /range|date|time|hour|minute|hidden/.test(attrType)) {
                el.on('change', function() {
                    if (self.boo.immediate == false) return;
                    var isPass = self.isPass($(this));
                    if (isPass) {
                        self.isError($(this), false);
                    }
                });
            } else {
                // 输入
                el.on({
                    focus: function() {
                        if (self.boo.immediate) setTimeout(function() {
                            $.validate.focusable = 0;
                            var isPass = self.isPass(el);
                            if (isPass) {
                                self.isError(el, false);
                            }
                        }, 20);
                    },
                    input: function() {
                        if (self.boo.immediate == false) return;
                        // IE10+下有个bug
                        // focus placeholder变化时候，会触发input事件，我只能呵呵了
                        if (document.msHidden != undefined && this.value == '' && !this.lastvalue && $(this).attr('placeholder')) {
                            this.lastvalue = this.value;

                            return;
                        }

                        $.validate.focusable = false;
                        var isPass = self.isPass($(this));
                        if (isPass) {
                            self.isError($(this), false);
                            if (window.errorTip) {
                                window.errorTip.hide();
                            }
                        }
                        this.lastvalue = this.value;
                    }
                });

                // IE7-IE8 模拟 input
                if ('oninput' in document.createElement('div') == false) {
                    ele.attachEvent('onpropertychange',  function(event) {
                        if (event && event.propertyName == 'value' && self.boo.immediate) {
                            $.validate.focusable = false;
                            var isPass = self.isPass($(ele));
                            if (isPass) {
                                self.isError($(ele), false);
                            }
                            $.validate.focusable = true;
                        }
                    });
                }
            }
        });

        form.data('immediate', true);

        return this;
    };

    /**
     * 表单验证是否有误的处理
     * @param  {Object}  el      需要验证的元素
     * @param  {Boolean} isError 是否有误（可以直接传递结果）
     * @return {Boolean}         返回验证结果
     */
    Validate.prototype.isError = function(el, isError) {
        // 是否有错
        if (!el || !el.get || !el.length) {
            return this;
        }
        var ele = el.get(0);
        var result = isError;

        if (typeof isError == 'undefined' && el.is(':disabled') == false) {
            var customValidate = el.data('customValidate');
            result = $.validate.isError(ele) || (customValidate && customValidate(el));
        }

        var target = $.validate.getTarget(el);

        if (result) {
            // 出错
            target.addClass('error');
        } else if (ele.type == 'radio' && ele.name) {
            this.el.form.find('input[type=radio][name="' + ele.name + '"]').each(function() {
                $.validate.getTarget($(this)).removeClass('error').removeAttr('aria-label');
            });
        } else {
            target.removeClass('error').removeAttr('aria-label');
        }

        return result;
    };

    /**
     * 是否通过验证的处理（单个元素）
     * @param  {Object}  el 验证的元素
     * @return {Boolean}    是否验证通过，true表示验证OK，false表示验证有问题
     */
    Validate.prototype.isPass = function(el) {
        if (!el || !el.get || !el.length) return this;
        var ele = el.get(0);
        var id = ele.id;
        var label = this.boo.label;

        // 是否使用自定义的验证和提示
        var customValidate = {
            label: label
        };
        if (id && this.data && this.data.length) {
            $.each(this.data, function(index, obj) {
                if (obj.id == id) {
                    // 最终的提示文字，是否借助label元素
                    // 参数传递路径: here -> $.validate.isAllPass -> $.validate.prompt
                    if (typeof obj.label != 'undefined') {
                        obj.label = label;
                    }
                    // 自定义验证参数
                    customValidate = obj;
                }
            });
        }

        var isPass = $.validate.isAllPass(el, customValidate);

        if (isPass == true && customValidate && customValidate.method) {
            var content = customValidate.method.call(ele, el);
            if (typeof content == 'string' && content !== '') {
                this.errorTip(el, content);
                isPass = false;
            }
            el.data('customValidate', customValidate.method);
        }

        // 回调触发
        this.callback[isPass ? 'success' : 'error'].call(this, el);

        return isPass;
    };

    /**
     * 表单所有元素验证通过的判断处理
     * @return {Boolean} 是否表单所有元素验证通过
     */
    Validate.prototype.isAllPass = function() {
        var self = this;
        var form = this.el.form;

        var isAllPass = true;

        $.validate.focusable = true;

        form.find(':input').each(function() {
            if (isAllPass == true && self.isPass($(this)) == false) {
                isAllPass = false;
            }
            if (self.boo.multiple) {
                // 一次验证多个元素，只有一个出提示，其余标红
                var input = this;
                if (!form.data('immediate')) {
                    // self.data是自定义的验证数据
                    $.each(self.data, function(index, obj) {
                        if (obj.id == input.id && obj.method) {
                            $(input).data('customValidate', obj.method);
                        }
                    });
                }

                self.isError($(this));
            }
        });

        // 当有过一次提交之后，开启即时验证
        if (!form.data('immediate') && self.boo.immediate) {
            self.immediate();
        }

        return isAllPass;

    };

    /**
     * 错误提示的出现和处理
     * @param  {Object} el      提示的元素
     * @param  {String} content 提示的内容
     * @return {Object}         返回当前实例
     */
    Validate.prototype.errorTip = function(el, content) {
        $.validate.errorTip(el, content);

        return this;
    };


    return Validate;
}));
